tool
class_name CSVFile
extends Reference

const DEFAULT_SEP = ","
const DEFAULT_QUOTE = '"'

var _filepath := ""

var _array := Array2D.new()
var _headers := {}
var _headers_array = []
var _types := {}

var _map := {}  #origin data

var _sep := DEFAULT_SEP
var _quote := DEFAULT_QUOTE
var _uses_map := true
var _model_name := ""

const ALLOW_TYPES = ["str", "u32", "f64", "bool", "json"]

var regex:RegEx
func _init():
	regex = RegEx.new()
	var _v = regex.compile("(\\,|\\r?\\n|\\r|^)(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|([^\"\\,\\r\\n]*))")

func _get(p_property):
	if _map.has(p_property):
		return _map[p_property]


func _set(p_property, p_value):
	if _map.has(p_property):
		_map[p_property] = p_value


func _get_property_list():
	var ret := []
	for a_key in _map:
		ret.append({"name": a_key, "type": typeof(_map[a_key])})
	return ret


func _get_key(p_row: Array) -> String:
	if not p_row:
		return ""
	return p_row[0]


func load_file(filepath: String):
	var f = File.new()
	var err = f.open(filepath, File.READ)
	if err != OK:
		return err

	_model_name = _parse_headers(f)

	while not f.eof_reached():
		var row = _parse_typed(f.get_line())
		if not row.size():
			continue
		if _uses_map:
			_map[_get_key(row)] = row
		_array.append_row(row)

	f.close()
	_filepath = filepath


func _parse_headers(f: File):
	var _desc = f.get_line()  #drop first desc line
	var header_line = f.get_line()
	var type_line = f.get_line()
	var headers = _parse_line(header_line)
	var types = _parse_line(type_line)
	_headers_array = headers
	if headers[0] != "id":
		push_error("First column must be 'id'")
		return []
	for i in range(headers.size()):
		_headers[headers[i]] = i

	for i in range(types.size()):
		_types[headers[i]] = types[i]
	var model_name = _parse_line(_desc)[0]
	return model_name


func _parse_typed(p_line: String):
	var row = Array(_parse_line(p_line))
	if not row.size():
		return row
	for i in range(_headers_array.size()):
		var key = _headers_array[i]
		if _types[key] == "str":
			row[i] = _parse_typed_value(row[i], "str")
		elif _types[key] == "u32":
			row[i] = _parse_typed_value(row[i], "u32")
		elif _types[key] == "f64":
			row[i] = _parse_typed_value(row[i], "f64")
		elif _types[key] == "bool":
			row[i] = _parse_typed_value(row[i], "bool")
		elif _types[key] == "json":
			row[i] = _parse_typed_value(row[i], "json")
		else:
			print("can not parse type ",key, _types[key])
	return row


func _parse_typed_value(p_value: String, p_type: String):
	if p_type == "str":
		return p_value
	elif p_type == "u32":
		if not p_value:
			p_value = "0"
		return int(p_value)
	elif p_type == "f64":
		if not p_value:
			p_value = "0"
		return float(p_value)
	elif p_type == "bool":
		if not p_value:
			p_value = ""  #default is false
		return bool(p_value)
	elif p_type == "json":
		if not p_value:
			p_value = "[]"
		return str2var(p_value)
	return p_value




func _parse_line(p_line: String):
	if not p_line.length():
		return []
	var test = regex.search_all(p_line)
	var matchs = []
	for item in test:
		var match_value = item.get_string(2)
		if not match_value:
			match_value = item.get_string(3)
		matchs.append(match_value)

	# var row = p_line.split(_sep, true)
	return matchs


func get_headers() -> Dictionary:
	return _headers


func get_map() -> Dictionary:
	return _map


func get_array2d() -> Array2D:
	return _array


func map_has_value(p_key: String, p_header: String) -> bool:
	return _map.has(p_key) and _headers.has(p_header)


func map_get_value(p_key: String, p_header: String):
	if not _uses_map:
		printerr("CSVFile is not using map, but 'get_map_value' was called")
		return null
	if not map_has_value(p_key, p_header):
		return null
	return _map[p_key][_headers[p_header]]


func map_set_value(p_key: String, p_header: String, p_value):
	if not _uses_map:
		printerr("CSVFile is not using map, but 'get_map_value' was called")
		return null
	if not map_has_value(p_key, p_header):
		return null
	_map[p_key][_headers[p_header]] = p_value
